/*

Michael Slater
Student ID: 0849647
Filename: sr.cpp

* Compiles with Borland
* Program takes large files
* Handles extra symbols
* My custom StringFunction class is an object and can be re-used
* Displays error messages for bad input, invalid files, etc
* Displays number of Full and Partial matches.

*/


#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// This class performs the compare and length functions
class StringFunctions {
    public:
        int length(const char *);
        bool fullCompare(const char *,const char *);
        bool partialCompare(char,char);
};

int main(int argc, char *argv[])
{
    cout << "Name: Michael Slater"<<endl;
    cout << "StudentId: 0849647"<<endl<<endl;

    if(argc<2)
    {
        cerr << endl<<"Missing program arguments:"<<endl;
        cerr << "input=FILENAME;search=SEARCH_STRING;replace=REPLACE_STRING"<<endl;
        return 1;
    }
    if(argc>2)
    {
        cerr << endl<<"Too many program arguments:"<<endl;
        cerr << "input=FILENAME;search=SEARCH_STRING;replace=REPLACE_STRING"<<endl;
        return 1;
    }
    bool goodInput = true;
    string input(argv[1]);

    int semi1 = input.find(";");
    int semi2 = input.find(";",semi1+1);
    int eq1 = input.find("=");
    int eq2 = input.find("=",eq1+1);
    int eq3 = input.find("=",eq2+1);


    if(input.find("input")!=0||input.find("search")!=semi1+1||input.find("replace")!=semi2+1)
    {
        goodInput = false;
        cerr << "Input string is not formatted correctly:"<<endl;
        cerr << "input=FILENAME;search=SEARCH_STRING;replace=REPLACE_STRING"<<endl;
        return 1;
    }

    string inFileName(input,eq1+1,semi1-eq1-1);
    string inSearchString(input,eq2+1,semi2-eq2-1);
    string inReplaceString(input,eq3+1,input.length()-eq3-1);

    //  input=FILENAME;search=SEARCH_STRING;replace=REPLACE_STRING
    int extFind = inFileName.find_last_of(".");
    string outFileName(inFileName.substr(0,extFind)+"-sr"+inFileName.substr(extFind));

    const char *inputFile = inFileName.c_str();
    const char *outputFile = outFileName.c_str();//inFileName.substr(0,extFind)+"-sr"+inFileName.substr(extFind+1);
    const char *searchString = inSearchString.c_str();;
    const char *replaceString = inReplaceString.c_str();


    // Input and Output File Objects
    ifstream fileStream;
    ofstream outStream;
    // Opens input file stream
    fileStream.open(inputFile);
    if(!fileStream)
    {// checks to see if stream was created successfully
        cerr << "Error: file could not be opened!" << endl;
        return 1; // Ends program with status code of 1
    }
    // Opens output file stream
    outStream.open(outputFile);
    if(!outStream)
    {// checks to see if stream was created successfully
        cerr << "Error: Output file cannot open stream!" << endl;
        return 1;
    }

    // StringFunctions object provides methods necessary to do custom char * operations
    StringFunctions str;

    // The length of the search String
    // Unsigned because length is always >=0
    unsigned int searchLength = str.length(searchString);
    // Amount to seek backwards in file if no match
    // I computed it here so it doesn't have to do the same math in each loop iteration
    int seekBack = (searchLength-1)*-1;
    // The length of the string that replaces a full match
    // Unsigned because length is always >=0
    unsigned int replaceLength = str.length(replaceString);
    // Keeps track of the number of full matches
    unsigned int fullMatches = 0;
    // Keeps track of the number of partial matches
    unsigned int partialMatches = 0;
    // Dynamic array initialized to length of searchString
    // This array will step through the file 1 character at a time and compare against the search string every step
    char *fileData = new char[searchLength];

    // Controls loop by detecting the end of file
    bool eof=false;
    while(!eof)
    {
        // Loops searchLength times to put file into fileData array parts at a time
        for(int i=0;i<searchLength&&!eof;i++)
        {
            // Temporarily store file read data
            char inputChar = fileStream.get();
            // Check to see if last read was End of File
            if(fileStream.eof())
            {
                eof = true;
                // Loop through remainder of fileData
                for(int z=0;z<i;z++)
                {
                    // Check remainder of data for partial matches
                    if(str.partialCompare(fileData[z],searchString[z]))
                        partialMatches++;
                    // Write the last bits
                    outStream.put(fileData[z]);
                }
                // flush stream
                outStream.flush();
            }
            else // if EOF was not thrown then add char to fileData array
                fileData[i] = inputChar;
        }

        // True if a match is found
        if(!eof&&str.fullCompare(searchString,fileData))
        {
            // Writes the replacement string to the output file
            outStream.write(replaceString,replaceLength);
            // Flushes output stream
            outStream.flush();
            // Records +1 Full matches found
            fullMatches++;
        }
        else
        if(!eof)
        {
            // Checks for a partial match
            if(str.partialCompare(fileData[0],searchString[0]))
                // Records Partial Matches +1
                partialMatches++;
            // Writes character to file
            outStream.put(fileData[0]);
            // Flushes write stream
            outStream.flush();
            // Sets file back (searchLength-1) characters since no match was found in read set
            fileStream.seekg(seekBack,ios::cur);
        }
    }
    // Close input and output streams
    fileStream.close();
    outStream.close();

    cout << endl<<"Full Matches: "<<fullMatches<<endl;
    cout << "Partial Matches: "<<partialMatches<<endl;
    cout << "Output File: "<< outputFile<<endl<<endl;

    // Frees the allocated memory of the only dynamic array
    delete [] fileData;

    // Program returns status 0 for successful termination
    return 0;
}


// Manipulates pointers to determine length of string
int StringFunctions::length(const char *s)
{
    // Creates new pointer at same place of s
    const char *c = s;
    // Loops until hits NULL (all "C-Style Strings" are NULL terminated)
    while(*++c);
    // Returns (End memory of string - beginning memory of string) to get length
    return c-s;
}

// Basic algorithm to compare 2 char *
bool StringFunctions::fullCompare(const char *s1,const char *s2)
{
    int len = length(s1);
    bool result = true;
    for(int i=0;result&&i<len;i++)
    {
        if(s1[i] != s2[i])
        {
            result = false;
        }
    }
    return result;
}

// Partial compare just checks the first character of each input
bool StringFunctions::partialCompare(char s1,char s2)
{
    return s1 == s2;
}
